package com.hong.drive.server.modules.file.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.hong.drive.core.exception.CloudDriveBusinessException;
import com.hong.drive.core.utils.FileUtils;
import com.hong.drive.core.utils.IdUtil;
import com.hong.drive.server.common.event.log.ErrorLogEvent;
import com.hong.drive.server.modules.file.context.FileChunkMergeAndSaveContext;
import com.hong.drive.server.modules.file.context.FileSaveContext;
import com.hong.drive.server.modules.file.context.QueryRealFileListContext;
import com.hong.drive.server.modules.file.entity.CloudDriveFile;
import com.hong.drive.server.modules.file.entity.CloudDriveFileChunk;
import com.hong.drive.server.modules.file.service.IFileChunkService;
import com.hong.drive.server.modules.file.service.IFileService;
import com.hong.drive.server.modules.file.mapper.CloudDriveFileMapper;
import com.hong.drive.storage.engine.core.StorageEngine;
import com.hong.drive.storage.engine.core.context.DeleteFileContext;
import com.hong.drive.storage.engine.core.context.MergeFileContext;
import com.hong.drive.storage.engine.core.context.StoreFileContext;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
* @author Zhihong
* @description 针对表【cloud_drive_file(物理文件信息表)】的数据库操作Service实现
* @createDate 2023-06-17 16:34:13
*/
@Service(value = "fileService")
public class FileServiceImpl extends ServiceImpl<CloudDriveFileMapper, CloudDriveFile> implements IFileService, ApplicationContextAware {

    @Autowired
    private StorageEngine storageEngine;

    @Autowired
    private IFileChunkService iFileChunkService;

    private ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    /**
     * 根据条件查询用户的实际文件列表
     *
     * @param context
     * @return
     */
    @Override
    public List<CloudDriveFile> getFileList(QueryRealFileListContext context) {
        Long userId = context.getUserId();
        String identifier = context.getIdentifier();
        QueryWrapper<CloudDriveFile> queryWrapper = new QueryWrapper<>();
        if (Objects.nonNull(userId)) {
            queryWrapper.eq("create_user", userId);
        }
        if (StringUtils.isNotBlank(identifier)) {
            queryWrapper.eq("identifier", identifier);
        }
        return list(queryWrapper);
    }

    /**
     * 上传单文件并保存实体记录
     * <p>
     * 1、上传单文件
     * 2、保存实体记录
     *
     * @param context
     */
    @Override
    public void saveFile(FileSaveContext context) {
        //上游：上传单文件
        storeMultipartFile(context);
        //下游：保存实体记录
        CloudDriveFile record = doSaveFile(context.getFilename(),//文件名
                context.getRealPath(),//文件路径
                context.getTotalSize(),//文件大小
                context.getIdentifier(),//文件唯一标识
                context.getUserId());//文件所属用户id
        context.setRecord(record);
    }

    /**
     * 合并物理文件并保存物理文件记录
     * <p>
     * 1、委托文件存储引擎合并文件分片
     * 2、保存物理文件记录
     *
     * @param context
     */
    @Override
    public void mergeFileChunkAndSaveFile(FileChunkMergeAndSaveContext context) {
        doMergeFileChunk(context);// 委托文件存储引擎合并文件分片
        CloudDriveFile record = doSaveFile(context.getFilename(), context.getRealPath(), context.getTotalSize(), context.getIdentifier(), context.getUserId());
        context.setRecord(record);
    }

    /************************************************private************************************************/

    /**
     * 委托文件存储引擎合并文件分片
     * <p>
     * 1、查询文件分片的记录（iFileChunkService根据唯一标识和创建用户查询得到分片记录）
     * 2、根据文件分片的记录去合并物理文件
     * 3、删除文件分片记录
     * 4、封装合并文件的真实存储路径到上下文信息中
     *
     * @param context
     */
    private void doMergeFileChunk(FileChunkMergeAndSaveContext context) {
        QueryWrapper<CloudDriveFileChunk> queryWrapper = Wrappers.query();
        queryWrapper.eq("identifier", context.getIdentifier());
        queryWrapper.eq("create_user", context.getUserId());
        queryWrapper.ge("expiration_time", new Date());
        // 查询文件分片的记录列表
        List<CloudDriveFileChunk> chunkRecoredList = iFileChunkService.list(queryWrapper);
        if (CollectionUtils.isEmpty(chunkRecoredList)) {
            throw new CloudDriveBusinessException("该文件未找到分片记录");
        }
        // 将文件分片的记录列表转换为文件分片的真实存储路径列表
        // 文件存储引擎并不关心文件分片的记录属于哪个用户，只关心文件分片的真实存储路径
        List<String> realPathList = chunkRecoredList.stream()
                .sorted(Comparator.comparing(CloudDriveFileChunk::getChunkNumber))// 按照文件分片的下标升序排序
                .map(CloudDriveFileChunk::getRealPath)
                .collect(Collectors.toList());

        // 委托文件存储引擎合并文件分片
        try {
            MergeFileContext mergeFileContext = new MergeFileContext();
            mergeFileContext.setFilename(context.getFilename());
            mergeFileContext.setIdentifier(context.getIdentifier());
            mergeFileContext.setUserId(context.getUserId());
            mergeFileContext.setRealPathList(realPathList);

            synchronized (context.getIdentifier()) {
                // 查File表，判断Identifier是否已经存在，如果存在，说明已经合并过了，不需要再合并了
                CloudDriveFile cloudDriveFile = getOne(Wrappers.<CloudDriveFile>lambdaQuery().eq(CloudDriveFile::getIdentifier, context.getIdentifier()));
                if (cloudDriveFile != null) {
                    // 说明已经合并过了，不需要再合并了
                    context.setRealPath(cloudDriveFile.getRealPath());
                } else {
                    storageEngine.mergeFile(mergeFileContext);
                    // 封装合并文件的真实存储路径到上下文信息中
                    context.setRealPath(mergeFileContext.getRealPath());
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
            throw new CloudDriveBusinessException("文件分片合并失败");
        }
        // 删除文件分片的记录
        List<Long> fileChunkRecordIdList = chunkRecoredList.stream().map(CloudDriveFileChunk::getId).collect(Collectors.toList());
        iFileChunkService.removeByIds(fileChunkRecordIdList);
    }
    
    /**
     * 上传单文件
     * 该方法委托文件存储引擎实现
     *
     * @param context
     */
    private void storeMultipartFile(FileSaveContext context) {
        try {
            StoreFileContext storeFileContext = new StoreFileContext();
            storeFileContext.setInputStream(context.getFile().getInputStream());
            storeFileContext.setFilename(context.getFilename());
            storeFileContext.setTotalSize(context.getTotalSize());
            storageEngine.store(storeFileContext);// 委托文件存储引擎实现
            context.setRealPath(storeFileContext.getRealPath());// 设置文件存储的真实路径
        } catch (IOException e) {
            e.printStackTrace();
            throw new CloudDriveBusinessException("文件上传失败");
        }
    }

    /**
     * 保存实体文件记录
     *
     * @param filename
     * @param realPath
     * @param totalSize
     * @param identifier
     * @param userId
     * @return
     */
    private CloudDriveFile doSaveFile(String filename, String realPath, Long totalSize, String identifier, Long userId) {
        CloudDriveFile record = assembleCloudDriveFile(filename, realPath, totalSize, identifier, userId);
        if (!save(record)) {
            try {
                DeleteFileContext deleteFileContext = new DeleteFileContext();
                deleteFileContext.setRealFilePathList(Lists.newArrayList(realPath));
                storageEngine.delete(deleteFileContext);
            } catch (IOException e) {
                e.printStackTrace();
                // 发布事件
                ErrorLogEvent errorLogEvent = new ErrorLogEvent(this, "文件物理删除失败，请执行手动删除！文件路径: " + realPath, userId);
                applicationContext.publishEvent(errorLogEvent);
            }
        }
        return record;
    }

    /**
     * 拼装文件实体对象
     *
     * @param filename
     * @param realPath
     * @param totalSize
     * @param identifier
     * @param userId
     * @return
     */
    private CloudDriveFile assembleCloudDriveFile(String filename, String realPath, Long totalSize, String identifier, Long userId) {
        CloudDriveFile record = new CloudDriveFile();

        record.setFileId(IdUtil.get());
        record.setFilename(filename);
        record.setRealPath(realPath);
        record.setFileSize(String.valueOf(totalSize));
        record.setFileSizeDesc(FileUtils.byteCountToDisplaySize(totalSize));
        record.setFileSuffix(FileUtils.getFileSuffix(filename));
        record.setIdentifier(identifier);
        record.setCreateUser(userId);
        record.setCreateTime(new Date());

        return record;
    }



}




